/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "Animation2D.h"
#include <Converter.h>
#include <utl.h>
#include <math/utl.h>
#include <xml/Saver.h>
#include <boost/bind.hpp>

namespace liba {
namespace lib3d {
namespace node {

const Atom Animation2D::node_name( "Animation2D" );
const Atom Animation2D::transparency_name( "Transparency" );
const Atom Animation2D::color_name( "Color" );
const Atom Animation2D::START_COMMAND_NAME("Start");

void Animation2D::CreateProxy(ProxyVisitor * proxyVisitor)
{
	proxyVisitor->CreateAnimation2DProxy(this);
}

namespace {
double ReturnZero() { return 0.0; }
} // anonymous namespace

Animation2D::Animation2D( const Atom & name, Node * parent )
	: Node2D( name, parent )
	, animation(name)
	, transparency( 0 )
	, color(Color(1, 1, 1))
	, x_align(CENTER)
	, y_align(CENTER)
	, need_update(true)
	, framePercent(&ReturnZero, boost::bind(&Animation2D::SetFramePercent, this, _1))
	, frameNumber(&ReturnZero, boost::bind(&Animation2D::SetFrameNumber, this, _1))
{
	this->RegisterProperty(anim2d::AnimatedSequences::frame_percent_name, &this->framePercent);
	this->RegisterProperty(anim2d::AnimatedSequences::frame_number_name, &this->frameNumber);
	this->RegisterProperty(transparency_name, &this->transparency);
	this->RegisterProperty(color_name, &this->color);
	this->startCommand.Invoked().connect(boost::bind(&Animation2D::StartCommandInvoked, this, _1));
	this->RegisterCommand(START_COMMAND_NAME, &this->startCommand);
	node_type = node_name;
	hard::Manager::add_dependent( this );
}

Animation2D::Animation2D(const Animation2D& rhs)
	: Node2D(rhs)
	, HardwareDependentObject<hard::Manager, hard::VertexBuffer>(rhs)
	, x_align(rhs.x_align)
	, y_align(rhs.y_align)
	, animation(rhs.animation)
	, transparency(rhs.transparency)
	, color(rhs.color)
	, need_update(true)
	, framePercent(&ReturnZero, boost::bind(&Animation2D::SetFramePercent, this, _1))
	, frameNumber(&ReturnZero, boost::bind(&Animation2D::SetFrameNumber, this, _1))
{
	this->RegisterProperty(anim2d::AnimatedSequences::frame_percent_name, &this->framePercent);
	this->RegisterProperty(anim2d::AnimatedSequences::frame_number_name, &this->frameNumber);
	this->RegisterProperty(transparency_name, &this->transparency);
	this->RegisterProperty(color_name, &this->color);
	this->startCommand.Invoked().connect(boost::bind(&Animation2D::StartCommandInvoked, this, _1));
	this->RegisterCommand(START_COMMAND_NAME, &this->startCommand);
	node_type = node_name;
	hard::Manager::add_dependent(this);
}

Animation2D::~Animation2D()
{
	hard::Manager::remove_dependent( this );
}

void Animation2D::on_save_attributes(xml::SaverBase * saver) const
{
	Node::on_save_attributes(saver);

	if(transparency > 0.0001)
		saver->save_attribute("Transparency", transparency.Get());

	if(color != Color(1, 1, 1))
		saver->save_attribute("Color", color.Get());

	if(x_align != CENTER)
		saver->save_attribute("AlignX", x_align);

	if(y_align != CENTER)
		saver->save_attribute("AlignY", y_align);

	if(!animation.GetStartSequence().empty())
		saver->save_attribute("Start", animation.GetStartSequence());
}

void Animation2D::on_save_nodes(xml::SaverBase * saver) const
{
	animation.on_save_nodes(saver);

	Node::on_save_nodes(saver);
}

void Animation2D::on_add_hardware(hard::Manager::hardware_type * ha)
{
	hard_vector.push_back( SweetPair(ha, ha->create_vertex_buffer()) );
}

bool Animation2D::on_node( xml::Provider * prov, const std::string & name )
{
	if( name == "Sequence" )
	{
		return animation.on_node(prov, name);
	}
	if( name == "Pause" )
	{
		return animation.on_node(prov, name);
	}
	return Node::on_node( prov, name );
}

bool Animation2D::on_attribute( xml::Provider * prov, const std::string & name, const std::string & value )
{
	if( name == "Transparency" )
	{
		double doubleValue;
		if (!converter::convert(value, &doubleValue))
			return false;
		this->transparency = doubleValue;
		return true;
	}
	if( name == "Color" )
	{
		Color colorValue;
		if (!converter::convert(value, &colorValue))
			return false;
		this->color = colorValue;
		return true;
	}
	if( name == "AlignX" )
		return converter::convert( value, &x_align );
	if( name == "AlignY" )
		return converter::convert( value, &y_align );
	if( name == "Start" )
	{
		animation.on_attribute(prov, name, value);
		return true;
	}

	return Node::on_attribute( prov, name, value );
}

bool Animation2D::on_end_node( xml::Provider * prov )
{
	animation.on_end_node(prov);
	fill_vb();
	return true;
}

void Animation2D::StartCommandInvoked(liba::Atom arg)
{
	start_animation(arg);
}

bool Animation2D::start_animation(const Atom & name, bool no_restart)
{
	if(no_restart && animation.get_animation_name() == name)
		return true;
	
	int oldWidth = animation.get_width();
	int oldHeight = animation.get_height();
	
	if(!animation.start_animation(name))
		return false;
	
	if(oldWidth != animation.get_width() || oldHeight != animation.get_height())
		need_update = true;
	
	return true;
}

bool Animation2D::needs_life_cycle() const
{
	return animation.needs_life_cycle();
}

void Animation2D::set_manual_time_control(bool manual)
{
	animation.set_manual_time_control(manual);
}

void Animation2D::set_time(double time)
{
	animation.set_time(time);
}

double Animation2D::get_animation_length()const
{
	return animation.get_animation_length();
}

void Animation2D::LifeCycle(time_float delta_time)
{
	animation.LifeCycle(delta_time);
}

void Animation2D::SetFramePercent(double value)
{
	animation.SetFramePercent(value);
}

void Animation2D::SetFrameNumber(double value)
{
	animation.SetFrameNumber(value);
}

void Animation2D::disable_loop()
{
	this->animation.disable_loop();
}

void Animation2D::RenderToList(hard::Hardware * hardware, hard::RenderList * render_list)
{
	if( !animation.is_active_sequence() )
		return;
	hard::VertexBuffer * vb = find_data( hardware );
	if( !vb->is_valid() || need_update)
		fill_vb();

	hard::RenderModel render_model;
	render_model.filter_texture = false;
	render_model.world = GetGlobalMatrix();
	render_model.vb = vb;
	render_model.primitive_start = 0;
	render_model.primitive_count = 4;
	render_model.vertex_start = 0;
	render_model.vertex_count = 12;
	render_model.material.diffuse = color;
	render_model.material.emissive = color;
	transparency.Set(math::clamp(transparency.Get(), 0.0001, 0.9999));
	render_model.material.transparency = transparency;
	render_model.material.specular_power = 0;
//	render_model.bounding_sphere = get_rep()->get_bounding_sphere();
	animation.render_to_list( hardware, &render_model.material.emissive_texture );
	render_model.filter_texture = true;
	render_list->models.push_back( render_model );
	return Node::RenderToList( hardware, render_list );
}

void Animation2D::fill_vb()
{
	if( !animation.is_active_sequence() )
		return;
	SimpleArray<float> positions(12*3);
	SimpleArray<float> normals(12*3);
	SimpleArray<float> uvs(12*2);
	float * pos_data = positions.data();
	float * nor_data = normals.data();
	float * uvs_data = uvs.data();

	float x = 0;
	float y = 0;
	float dx = animation.get_width();
	float dy = animation.get_height();
	float u = 0;
	float v = 0;
	float du = 1;
	float dv = 1;

	switch(x_align)
	{
	case LEFT:
		break;
	case RIGHT:
		x = -dx;
		break;
	default:
		x = -dx/2;
		break;
	}

	switch(y_align)
	{
	case BOTTOM:
		break;
	case TOP:
		y = -dy;
		break;
	default:
		y = -dy/2;
		break;
	}

	//CCW
	*pos_data++ = x;
	*pos_data++ = y;
	*pos_data++ = 0;
	*uvs_data++ = u;
	*uvs_data++ = v;

	*pos_data++ = x;
	*pos_data++ = y + dy;
	*pos_data++ = 0;
	*uvs_data++ = u;
	*uvs_data++ = v + dv;

	*pos_data++ = x + dx;
	*pos_data++ = y;
	*pos_data++ = 0;
	*uvs_data++ = u + du;
	*uvs_data++ = v;

	*pos_data++ = x + dx;
	*pos_data++ = y;
	*pos_data++ = 0;
	*uvs_data++ = u + du;
	*uvs_data++ = v;

	*pos_data++ = x;
	*pos_data++ = y + dy;
	*pos_data++ = 0;
	*uvs_data++ = u;
	*uvs_data++ = v + dv;

	*pos_data++ = x + dx;
	*pos_data++ = y + dy;
	*pos_data++ = 0;
	*uvs_data++ = u + du;
	*uvs_data++ = v + dv;

	for( int i = 0; i < 6; ++i )
	{
		*nor_data++ = 0;
		*nor_data++ = 0;
		*nor_data++ = 1;
	}

	//CW
	*pos_data++ = x;
	*pos_data++ = y;
	*pos_data++ = 0;
	*uvs_data++ = u;
	*uvs_data++ = v;

	*pos_data++ = x + dx;
	*pos_data++ = y;
	*pos_data++ = 0;
	*uvs_data++ = u + du;
	*uvs_data++ = v;

	*pos_data++ = x;
	*pos_data++ = y + dy;
	*pos_data++ = 0;
	*uvs_data++ = u;
	*uvs_data++ = v + dv;

	*pos_data++ = x + dx;
	*pos_data++ = y;
	*pos_data++ = 0;
	*uvs_data++ = u + du;
	*uvs_data++ = v;

	*pos_data++ = x + dx;
	*pos_data++ = y + dy;
	*pos_data++ = 0;
	*uvs_data++ = u + du;
	*uvs_data++ = v + dv;

	*pos_data++ = x;
	*pos_data++ = y + dy;
	*pos_data++ = 0;
	*uvs_data++ = u;
	*uvs_data++ = v + dv;

	for( int i = 0; i < 6; ++i )
	{
		*nor_data++ = 0;
		*nor_data++ = 0;
		*nor_data++ = -1;
	}

	SimpleArray<float> empty_float;
	SimpleArray<int> empty_int;

	HardVector::iterator it = hard_vector.begin();
	for( ;it != hard_vector.end(); ++it )
	{
		if( !it->second->update( positions,
			normals, empty_float,
			uvs, uvs ) )
		{
			THROW(Gluck(__FUNCTION__, "it->second->update() failed"));
		}
	}
	
	need_update = true;
}

Node* Animation2D::Clone()
{
	return new Animation2D(*this);
}

converter::EnumPair<char, Animation2D::AlignmentType> Animation2D::AlignmentTypeNames[] = 
{
	converter::MakeEnumPair( "CENTER", Animation2D::CENTER ),
	converter::MakeEnumPair( "LEFT", Animation2D::LEFT ),
	converter::MakeEnumPair( "RIGHT", Animation2D::RIGHT ),
	converter::MakeEnumPair( "TOP", Animation2D::TOP ),
	converter::MakeEnumPair( "BOTTOM", Animation2D::BOTTOM ),
	0
};

} // namespace node
} // namespace lib3d
} // namespace liba

